package org.vison.wonfu.framework.soa.protocol;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vison.wonfu.framework.commons.Constants;
import org.vison.wonfu.framework.soa.*;
import org.vison.wonfu.framework.soa.annotation.SOAParamsMode;
import org.vison.wonfu.framework.soa.annotation.SOAResultMode;
import org.vison.wonfu.framework.soa.annotation.SoaRequestMethod;
import org.vison.wonfu.framework.soa.context.RpcContext;

import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;


public  class RpcInvocation implements Invocation {


    private String application;
    private String protocolServiceKey;
    /**
     * 调用的目标应用appid
     */
    private String remoteApplication;
    private String commandKey;
    private String serviceCommandKey;
    private String appCommandKey;
    private String serviceName;
    private String version;
    private String methodName;

    /**
     * host
     */
    private String host;

    /**
     * port
     */
    private int port;

    /**
     * https
     */
    private boolean https;

    /**
     * service path
     */
    private String servicePath;

    /**
     * method path
     */
    private String methodPath;

    private transient Class<?>[] parameterTypes;
    private Object[] arguments;
    private Map<String,Object> fixedParams;

    private String parameterTypesDesc;
    private String[] compatibleParamSignatures;
    private transient Class<?> returnType;
    private transient Type[] returnTypes;

    private transient Invoker<?> invoker;
    private transient InvokeMode invokeMode;
    private Integer timeout;
    private Integer connectionTimeout;

    private Map<String, String> extraHeader;

    private SoaRequestMethod soaRequestMethod;

    /**
     * Only used on the caller side, will not appear on the wire.
     */
    private transient   Map<Object, Object> attributes;

    private Class<?> fallback;

    private Protocol protocol;

    public RpcInvocation(Protocol protocol) {
        this.protocol = protocol;
        this.attributes = new ConcurrentHashMap<>();
    }

    private void initParameterDesc() {

    }


    public void setAttributes(Map<Object, Object> attributes) {
        this.attributes = attributes;
    }

    public void setApplication(String application) {
        this.application = application;
    }

    @Override
    public Invoker<?> getInvoker() {
        return invoker;
    }

    @Override
    public void setInvoker(Invoker<?> invoker) {
        this.invoker = invoker;
    }

    @Override
    public InvokeMode getInvokeMode() {
        return invokeMode;
    }

    @Override
    public void setInvokeMode(InvokeMode invokeMode) {
        this.invokeMode = invokeMode;
    }

    @Override
    public String getProtocolServiceKey() {
        return protocolServiceKey;
    }

    public void setProtocolServiceKey(String protocolServiceKey) {
        this.protocolServiceKey = protocolServiceKey;
    }

    @Override
    public String getRemoteApplication() {
        return remoteApplication;
    }

    @Override
    public void setRemoteApplication(String remoteApplication) {
        this.remoteApplication = remoteApplication;
    }

    @Override
    public String getCommandKey() {
        return commandKey;
    }

    public void setCommandKey(String commandKey) {
        this.commandKey = commandKey;
    }

    @Override
    public String getServiceCommandKey() {
        return serviceCommandKey;
    }

    public void setServiceCommandKey(String serviceCommandKey) {
        this.serviceCommandKey = serviceCommandKey;
    }

    @Override
    public String getAppCommandKey() {
        return appCommandKey;
    }

    public void setAppCommandKey(String appCommandKey) {
        this.appCommandKey = appCommandKey;
    }

    @Override
    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    @Override
    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    @Override
    public String getGrayVersion() {
        return getRawAttachmentString(Constants.GRAY_VERSION_KEY);
    }

    @Override
    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    @Override
    public Class<?>[] getParameterTypes() {
        return parameterTypes;
    }

    public void setParameterTypes(Class<?>[] parameterTypes) {
        this.parameterTypes = parameterTypes == null ? new Class<?>[0] : parameterTypes;
    }

    @Override
    public String getParameterTypesDesc() {
        return parameterTypesDesc;
    }

    @Override
    public String[] getCompatibleParamSignatures() {
        return compatibleParamSignatures;
    }

    @Override
    public Object[] getArguments() {
        return arguments;
    }

    public void setArguments(Object[] arguments) {
        this.arguments = arguments == null ? new Object[0] : arguments;
    }

    @Override
    public Class<?> getReturnType() {
        return returnType;
    }

    public void setReturnType(Class<?> returnType) {
        this.returnType = returnType;
    }

    @Override
    public Type[] getReturnTypes() {
        return returnTypes;
    }

    public void setReturnTypes(Type[] returnTypes) {
        this.returnTypes = returnTypes;
    }

    @Override
    public Map<Object, Object> getAttributes() {
        return attributes;
    }

    @Override
    public Object put(Object key, Object value) {
        return attributes.put(key, value);
    }

    @Override
    public Object putIfAbsent(Object key, Object value) {
        return attributes.putIfAbsent(key, value);
    }

    @Override
    public Object get(Object key) {
        return attributes.get(key);
    }

    @Override
    public Object get(String key, Object defaultValue) {
        return attributes.getOrDefault(key, defaultValue);
    }

    @Override
    public String getString(String key, String defaultValue) {
        return Optional.ofNullable(attributes.get(key))
                .filter(v -> v instanceof String)
                .map(Object::toString)
                .orElse(defaultValue);
    }

    public void setExtraHeader(Map<String, String> extraHeader) {
        this.extraHeader = extraHeader;
    }

    public Integer getTimeout() {
        return timeout;
    }

    public void setTimeout(Integer timeout) {
        this.timeout = timeout;
    }

    public void setConnectionTimeout(Integer connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    public Integer getConnectionTimeout() {
        return connectionTimeout;
    }

    @Override
    public ConcurrentMap<String, Object> getAttachments() {
        if (isProviderSide()) {
            return RpcContext.getServerAttachment().getAttachments();
        }
        return RpcContext.getClientAttachment().getAttachments();
    }

    @Override
    public String getAttachmentString(String key, String defaultValue) {
        return Optional.ofNullable(getAttachments())
                .map(a -> a.get(key))
                .filter(v -> v instanceof String)
                .map(Object::toString)
                .orElse(defaultValue);
    }

    @Override
    public Map<String, Object> getRawAttachments() {

        ConcurrentMap<String, Object> rawAttachments = new ConcurrentHashMap<>();

        if (isProviderSide()) {
            rawAttachments = RpcContext.getServiceContext().getAttachments();
        } else {
            rawAttachments = Stream.of(RpcContext.getServiceContext().getAttachments(), RpcContext.getClientAttachment().getRawAttachments())
                    .flatMap(map -> map.entrySet().stream())
                    .collect(Collectors.toConcurrentMap(
                            Map.Entry::getKey,
                            Map.Entry::getValue,
                            (v1, v2) -> v2));
        }
        return Collections.unmodifiableMap(rawAttachments);
    }

    private Object getRawAttachmentObject(String key, Object defaultValue) {

        if (isProviderSide()) {
            return RpcContext.getServiceContext().getAttachments().getOrDefault(key, defaultValue);
        }

        // Consumer side
        Object value = RpcContext.getClientAttachment().getRawAttachments().get(key);
        if (Objects.isNull(value)) {
            value = RpcContext.getServiceContext().getAttachments().getOrDefault(key, defaultValue);
        }
        return value;
    }

    @Override
    public void putRawAttachment(String key, Object value) {
        if (isConsumerSide()) {
            getClientRawAttachments().put(key, value);
        }
    }

    @Override
    public void putRawAttachmentIfAbsent(String key, Object value) {
        if (isConsumerSide()) {
            getClientRawAttachments().putIfAbsent(key, value);
        }
    }

    @Override
    public Object getRawAttachment(String key, Object defaultValue) {
        return getRawAttachmentObject(key, defaultValue);
    }

    @Override
    public String getRawAttachmentString(String key, String defaultValue) {
        return Optional.ofNullable(getRawAttachment(key))
                .filter(v -> v instanceof String)
                .map(Object::toString)
                .orElse(defaultValue);
    }

    @Override
    public void putDMCLog(String key, ThrowableSupplier<Object> value, Level level) {
        Logger logger = LoggerFactory.getLogger(this.commandKey);

        Object originalValue;
//        if (level.ordinal() >= logger.getLevel().ordinal() && Objects.nonNull(originalValue = value.getIgnoreThrowable())) {
//            attributeLevelMDCLogs().put(key, originalValue);
//        }
    }

    @Override
    public void putDMCLog(String key, Object value) {
        if (Objects.nonNull(value)) {
            attributeBaseMDCLogs().put(key, value);
        }
    }

    @Override
    public Map<String, Object> getBaseMDCLogs() {
        return attributeBaseMDCLogs();
    }

    @Override
    public Map<String, Object> getLevelMDCLogs() {
        return attributeLevelMDCLogs();
    }

    @Override
    public boolean isProviderSide() {
        return getAttributes().containsKey(SoaConstants.PROVIDER_MODEL);
    }

    @Override
    public boolean isConsumerSide() {
        return getAttributes().containsKey(SoaConstants.CONSUMER_MODEL);
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> attributeBaseMDCLogs() {
        return (Map<String, Object>) getAttributes().computeIfAbsent(Constants.DYNAMIC_LOG_BASE_KEY, (Function<Object, Map<String, Object>>) o -> new LinkedHashMap<>());
    }

    @SuppressWarnings("unchecked")
    private Map<String, Object> attributeLevelMDCLogs() {
        return (Map<String, Object>) getAttributes().computeIfAbsent(Constants.DYNAMIC_LOG_LEVEL_KEY, (Function<Object, Map<String, Object>>) o -> new LinkedHashMap<>());
    }

    private ConcurrentMap<String, Object> getClientRawAttachments() {
        return RpcContext.getClientAttachment().getRawAttachments();
    }

    @Override
    public boolean isHttps() {
        return https;
    }

    @Override
    public void setHttps(boolean https) {
        this.https = https;
    }

    @Override
    public void setSoaRequestMethod(SoaRequestMethod soaRequestMethod) {
        this.soaRequestMethod = soaRequestMethod;
    }

    @Override
    public SoaRequestMethod getSoaRequestMethod() {
        return soaRequestMethod;
    }

    @Override
    public SOAParamsMode getParamsMode() {
        return null;
    }

    @Override
    public void setParamsMode(SOAParamsMode paramsMode) {

    }

    @Override
    public SOAResultMode getResultMode() {
        return null;
    }

    @Override
    public void setResultMode(SOAResultMode resultMode) {

    }

    @Override
    public boolean isCompleteResult() {
        return false;
    }

    @Override
    public void setCompleteResult(boolean completeResult) {

    }

    @Override
    public Map<String, Object> getFixedParams() {
        return fixedParams;
    }

    @Override
    public String getMethodPath() {
        return methodPath;
    }

    @Override
    public String getServicePath() {
        return servicePath;
    }

    public void setServicePath(String servicePath) {
        this.servicePath = servicePath;
    }

    @Override
    public List<String> getParamsName() {
        return null;
    }

    @Override
    public String getHost() {
        return host;
    }

    @Override
    public void setHost(String host) {
        this.host = host;
    }

    @Override
    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setMethodPath(String methodPath) {
        this.methodPath = methodPath;
    }

    public void setParameterTypesDesc(String parameterTypesDesc) {
        this.parameterTypesDesc = parameterTypesDesc;
    }

    public void setCompatibleParamSignatures(String[] compatibleParamSignatures) {
        this.compatibleParamSignatures = compatibleParamSignatures;
    }

    @Override
    public Map<String, String> getExtraHeader() {
        return null;
    }


    public void setFallback(Class<?> fallback) {
        this.fallback = fallback;
    }

    @Override
    public Class<?> getFallback() {
        return fallback;
    }

    @Override
    public Protocol getProtocol() {
        return protocol;
    }

    public void setProtocol(Protocol protocol) {
        this.protocol = protocol;
    }
}
